---
id: dbcontext-repository
title: 9.4 仓储模式
sidebar_label: 9.4 仓储模式 (Repository)
---

## 9.4.1 什么是仓储

> 在领域层和数据映射层的中介,使用类似集合的接口来存取领域对象，实际上，仓储被用于领域对象在数据库上的操作（实体 Entity 和值对象 Value types）。一般来说,我们针对不同的实体(或聚合根 Aggregate Root)会创建相对应的仓储。

简单来说，仓储就是数据存取操作的载体，但不限定于数据库。

## 9.4.1 内置仓储

`Furion` 框架内置了一个数据库操作的仓储，方便大家拓展和集成：

:::info 关于依赖注入说明

目前能够被依赖注入解析服务的仓储有：

- `IRepository`
- `IRepository<TEntity>`
- `IRepository<TEntity, TDbContextLocator>`
- `ISqlRepository`
- `ISqlRepository<TDbContextLocator>`
- `IMSRepository`
- `IMSRepository<TMasterDbContextLocator>`
- `IMSRepository<TMasterDbContextLocator,...TSlaveDbContextLocator8>`
- `IDbRepository<TDbContextLocator>`

还有两个私有仓储，也是所有仓储的基类（用于高级自定义开发）

- `IPrivateRepository<TEntity>`：所有实体仓储的基类
- `IPrivateSqlRepository`：所有数据库操作的基类

除此之后的所有仓储只能通过 `rep.Constraint<TRepository>()` 进行约束创建，如，只读仓储：

```cs
var readRepository = rep.Constraint<IReadableRepository<TEntity>>();
```

:::

### 9.4.1.1 非泛型超级仓储

- `IRepository`：默认非泛型仓储接口，支持切换到任何仓储
- `EFCoreRepository`：默认非泛型仓储实现

### 9.4.1.2 泛型实体仓储

- `IRepository<TEntity>`：默认数据库实体仓储接口
- `EFCoreRepository<TEntity>`：默认数据库实体仓储实现

### 9.4.1.3 泛型多数据库实体仓储

- `IRepository<TEntity, TDbContextLocator>`：任意数据库的实体仓储接口
- `EFCoreRepository<TEntity, TDbContextLocator>`：任意数据库的实体仓储实现

### 9.4.1.4 `Sql` 操作仓储

- `ISqlRepository`：默认数据库 `Sql` 操作仓储接口
- `SqlRepository`：默认数据库 `Sql` 操作仓储实现

### 9.4.1.5 多数据库 `Sql` 操作仓储

- `ISqlRepository<TDbContextLocator>`：任意数据库的 `Sql` 操作仓储接口
- `SqlRepository<TDbContextLocator>`：任意数据库的 `Sql` 操作仓储实现

### 9.4.1.6 只读实体仓储（支持多库）

- `IReadableRepository<TEntity>`：默认数据库只读实体仓储接口
- `IReadableRepository<TEntity, TDbContextLocator>`：多数据库只读实体仓储实现

### 9.4.1.7 只写实体仓储（支持多库）

- `IWritableRepository<TEntity>`：默认数据库只写实体仓储接口
- `IWritableRepository<TEntity, TDbContextLocator>`：多数据库只写实体仓储实现

### 9.4.1.8 只允许新增实体仓储（支持多库）

- `IInsertableRepository<TEntity>`：默认数据库只允许新增的实体仓储接口
- `IInsertableRepository<TEntity, TDbContextLocator>`：多数据库只允许新增的实体仓储实现

### 9.4.1.9 只允许更新实体仓储（支持多库）

- `IUpdateableRepository<TEntity>`：默认数据库只允许更新的实体仓储接口
- `IUpdateableRepository<TEntity, TDbContextLocator>`：多数据库只允许更新的实体仓储实现

### 9.4.1.10 只允许删除实体仓储（支持多库）

- `IDeletableRepository<TEntity>`：默认数据库只允许删除的实体仓储接口
- `IDeletableRepository<TEntity, TDbContextLocator>`：多数据库只允许删除的实体仓储实现

### 9.4.1.11 只允许拓展操作实体仓储（支持多库）

:::warning 功能移除声明

该功能在 `Furion 2.5.1 +` 版本中已移除。此操作让很多不了解 `EFCore` 的开发者产生了很大的误解，不知何时新增或何时更新，故移除此功能。

:::

- `IOperableRepository<TEntity>`：默认数据库只允许拓展操作实体仓储接口
- `IOperableRepository<TEntity, TDbContextLocator>`：多数据库只允许拓展操作实体仓储实现

### 9.4.1.12 只允许 `Sql` 查询仓储（支持多库）

- `ISqlReaderRepository`：默认数据库只允许 `Sql` 查询仓储接口
- `ISqlReaderRepository<TDbContextLocator>`：多数据库只允许 `Sql` 查询仓储实现

### 9.4.1.13 只允许 `Sql` 非查询仓储（支持多库）

- `ISqlExecutableRepository`：默认数据库只允许 `Sql` 非查询仓储接口
- `ISqlExecutableRepository<TDbContextLocator>`：多数据库只允许 `Sql` 非查询仓储实现

### 9.4.1.14 读写分离仓储

- `IMSRepository`：最多支持 **一主 7 从** 仓储

### 9.4.1.15 定位器仓储

- `IDbRepository<TDbContextLocator>`：初始化特定数据库仓储

## 9.4.2 仓储使用

`Furion` 提供了非常多的方式创建仓储，目的是为了让大家可以在不同的场景中使用。

### 9.4.2.1 构造函数注入

```cs {2}
private readonly IRepository<Person> _personRepository;
public FurionService(IRepository<Person> personRepository)
{
    _personRepository = personRepository;
}
```

### 9.4.2.2 方法参数注入

```cs {1}
public async Task<List<PersonDto>> GetAll([FromServices] IRepository<Person> repository, string keyword)
{
    var persons = await repository.AsQueryable().ToListAsync();
    return persons.Adapt<List<PersonDto>>();
}
```

### 9.4.2.3 `Db.GetRepository` 获取

```cs
// 非泛型仓储
var repository = Db.GetRepository();

// 泛型仓储
var repository = Db.GetRepository<Person>();

// Sql 仓储
var sqlRepository = Db.GetSqlRepository();
```

:::important 特别说明

不管采用哪种方式，`Furion` 都保证了仓储一次请求唯一性。同时 `Db.GetRepository<TEntity>()` 方式支持任何静态类中使用。

:::

## 9.4.3 仓储高级用法

### 9.4.3.1 动态切换实体仓储

```cs
var userRepository = personRepository.Change<User>();
```

### 9.4.3.2 动态切换仓储类型

比如，读写分离/主从库仓储：

```cs
// 只读仓储
var readRepository = personRepository.Constraint<IReadableRepository<User>>();

// 只写仓储
var writeRepository = personRepository.Constraint<IWritableRepository<User>>();
```

:::tip 小知识

`.Constraint` 支持切换任何仓储类型。

:::

### 9.4.3.3 获取 `Sql` 操作仓储

```cs
var sqlRepository = repository.Sql();
```

## 9.4.4 多数据库操作

`Furion` 通过 `DbContextLocator` 数据库上下文定位器实现多种数据库操作，可以随意切换数据库

### 9.4.4.1 动态切换多个数据库

#### 动态切换数据库

```cs
// 切换到 MSSQL 操作 Person表
var mssqlRepository = repository.Change<Person, MsSqlDbContextLocator>();

// 切换到 MySql 操作 Person表
var mysqlRepository = repository.Change<Person, MySqlDbContextLocator>();

// 切换到 Sqlite 操作 Person表
var sqliteRepository = repository.Change<Person, SqliteDbContextLocator>();

// 其他更多数据库一样的操作
```

#### 另外任何仓储或实体配置都支持多个数据库同时操作

仓储方式

```cs
IRepository<Person, MsSqlDbContextLocator> mssqlRepository

ISqlRepository<MsSqlDbContextLocator> mssqlRepository;
```

动态 `sql` 方式

```cs
"select * from person".Change<MsSqlDbContextLocator>().SqlQuery();
```

实体配置方式

```cs
public class User:Entity<int, MsSqlDbContextLocator, MySqlDbContextLocator>
{
}
```

`Sql` 代理方式

```cs
[SqlFunction("funcName", DbContextLocator = typeof(MySqlDbContextLocator))]
int GetAge(int id);
```

`Linq` 中方式

```cs
[QueryableFunction("funcName","dbo", DbContextLocator = typeof(MySqlDbContextLocator))]
string GetName()=> throw Oops.Oh("不支持该数据库操作");
```

## 9.4.5 在后台任务中使用

由于 `DbContext` 默认注册为 `Scoped` 生存周期，所以在后台任务中使用 `IServiceScopeFactory` 获取所有服务，如：

```cs
public class JobService : BackgroundService
{
    // 日志对象
    private readonly ILogger<JobService> _logger;

    // 服务工厂
    private readonly IServiceScopeFactory _scopeFactory;
    public JobService(ILogger<JobService> logger
        , IServiceScopeFactory scopeFactory)
    {
        _logger = logger;
        _scopeFactory = scopeFactory;
    }

    protected override Task ExecuteAsync(CancellationToken stoppingToken)
    {
        _logger.LogInformation("写日志~~");

        using (var scope = _scopeFactory.CreateScope())
        {
            var services = scope.ServiceProvider;

            // 获取数据库上下文
            var dbContext = Db.GetDbContext(services);

            // 获取仓储
            var respository = Db.GetRepository<Person>(services);

            // 解析其他服务
            var otherService = services.GetService<XXX>();
        }

        return Task.CompletedTask;
    }
}
```

:::important 数据库操作注意

如果作用域中对**数据库有任何变更操作**，需手动调用 `SaveChanges` 或带 `Now` 结尾的方法。也可以使用 `Scoped.CreateUow(handler)` 代替。

:::

## 9.4.6 自定义仓储

有些时候我们需要自定义仓储，拓展现有的仓储功能，可参考以下代码（含定位器仓储和默认仓储实现）

```cs
/// <summary>
/// 自定义仓储接口
/// </summary>
/// <typeparam name="TEntity"></typeparam>
/// <typeparam name="TDbContextLocator"></typeparam>
public interface IMyRepository<TEntity, TDbContextLocator> : IPrivateRepository<TEntity>
    where TEntity : class, IPrivateEntity, new()
    where TDbContextLocator : class, IDbContextLocator
{
    /// <summary>
    /// 自定义方法
    /// </summary>
    void MyMethod();
}

/// <summary>
/// 自定义仓储实现类
/// </summary>
/// <typeparam name="TEntity"></typeparam>
/// <typeparam name="TDbContextLocator"></typeparam>
public class MyRepository<TEntity, TDbContextLocator> : PrivateRepository<TEntity>, IMyRepository<TEntity, TDbContextLocator>, IScoped
    where TEntity : class, IPrivateEntity, new()
    where TDbContextLocator : class, IDbContextLocator
{
    /// <summary>
    /// 实现基类构造函数
    /// </summary>
    /// <param name="serviceProvider"></param>
    public MyRepository(IServiceProvider serviceProvider)
        : base(typeof(TDbContextLocator), serviceProvider)
    {
    }

    /// <summary>
    /// 自定义方法
    /// </summary>
    public void MyMethod()
    {
        throw new System.NotImplementedException();
    }
}

/// <summary>
/// 默认数据库自定义仓储接口
/// </summary>
/// <typeparam name="TEntity"></typeparam>
public interface IMyRepository<TEntity> : IMyRepository<TEntity, MasterDbContextLocator>
    where TEntity : class, IPrivateEntity, new()
{
}

/// <summary>
/// 默认数据库自定义仓储实现
/// </summary>
/// <typeparam name="TEntity"></typeparam>
public class MyRepository<TEntity> : MyRepository<TEntity, MasterDbContextLocator>, IMyRepository<TEntity>, IScoped
    where TEntity : class, IPrivateEntity, new()
{
    public MyRepository(IServiceProvider serviceProvider) : base(serviceProvider)
    {
    }
}
```

## 9.4.7 反馈与建议

:::note 与我们交流

给 Furion 提 [Issue](https://gitee.com/dotnetchina/Furion/issues/new?issue)。

:::
